/*
Copyright 2023 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package request

// Copy from https://github.com/aws/aws-sdk-go
// May have been modified by Beijing Volcanoengine Technology Ltd.

import (
	"fmt"
	"strings"
)

// A Handlers provides a collection of request handlers for various
// stages of handling requests.
type Handlers struct {
	Validate         HandlerList
	Build            HandlerList
	Sign             HandlerList
	Send             HandlerList
	ValidateResponse HandlerList
	Unmarshal        HandlerList
	UnmarshalStream  HandlerList
	UnmarshalMeta    HandlerList
	UnmarshalError   HandlerList
	Retry            HandlerList
	AfterRetry       HandlerList
	CompleteAttempt  HandlerList
	Complete         HandlerList
}

// Copy returns a copy of this handler's lists.
func (h *Handlers) Copy() Handlers {
	return Handlers{
		Validate:         h.Validate.copy(),
		Build:            h.Build.copy(),
		Sign:             h.Sign.copy(),
		Send:             h.Send.copy(),
		ValidateResponse: h.ValidateResponse.copy(),
		Unmarshal:        h.Unmarshal.copy(),
		UnmarshalStream:  h.UnmarshalStream.copy(),
		UnmarshalError:   h.UnmarshalError.copy(),
		UnmarshalMeta:    h.UnmarshalMeta.copy(),
		Retry:            h.Retry.copy(),
		AfterRetry:       h.AfterRetry.copy(),
		CompleteAttempt:  h.CompleteAttempt.copy(),
		Complete:         h.Complete.copy(),
	}
}

// Clear removes callback functions for all handlers.
func (h *Handlers) Clear() {
	h.Validate.Clear()
	h.Build.Clear()
	h.Send.Clear()
	h.Sign.Clear()
	h.Unmarshal.Clear()
	h.UnmarshalStream.Clear()
	h.UnmarshalMeta.Clear()
	h.UnmarshalError.Clear()
	h.ValidateResponse.Clear()
	h.Retry.Clear()
	h.AfterRetry.Clear()
	h.CompleteAttempt.Clear()
	h.Complete.Clear()
}

// IsEmpty returns if there are no handlers in any of the handlerlists.
func (h *Handlers) IsEmpty() bool {
	if h.Validate.Len() != 0 {
		return false
	}
	if h.Build.Len() != 0 {
		return false
	}
	if h.Send.Len() != 0 {
		return false
	}
	if h.Sign.Len() != 0 {
		return false
	}
	if h.Unmarshal.Len() != 0 {
		return false
	}
	if h.UnmarshalStream.Len() != 0 {
		return false
	}
	if h.UnmarshalMeta.Len() != 0 {
		return false
	}
	if h.UnmarshalError.Len() != 0 {
		return false
	}
	if h.ValidateResponse.Len() != 0 {
		return false
	}
	if h.Retry.Len() != 0 {
		return false
	}
	if h.AfterRetry.Len() != 0 {
		return false
	}
	if h.CompleteAttempt.Len() != 0 {
		return false
	}
	if h.Complete.Len() != 0 {
		return false
	}

	return true
}

// A HandlerListRunItem represents an entry in the HandlerList which
// is being run.
type HandlerListRunItem struct {
	Index   int
	Handler NamedHandler
	Request *Request
}

// A HandlerList manages zero or more handlers in a list.
type HandlerList struct {
	list []NamedHandler

	// Called after each request handler in the list is called. If set
	// and the func returns true the HandlerList will continue to iterate
	// over the request handlers. If false is returned the HandlerList
	// will stop iterating.
	//
	// Should be used if extra logic to be performed between each handler
	// in the list. This can be used to terminate a list's iteration
	// based on a condition such as error like, HandlerListStopOnError.
	// Or for logging like HandlerListLogItem.
	AfterEachFn func(item HandlerListRunItem) bool
}

// A NamedHandler is a struct that contains a name and function callback.
type NamedHandler struct {
	Name string
	Fn   func(*Request)
}

// copy creates a copy of the handler list.
func (l *HandlerList) copy() HandlerList {
	n := HandlerList{
		AfterEachFn: l.AfterEachFn,
	}
	if len(l.list) == 0 {
		return n
	}

	n.list = append(make([]NamedHandler, 0, len(l.list)), l.list...)
	return n
}

// Clear clears the handler list.
func (l *HandlerList) Clear() {
	l.list = l.list[0:0]
}

// Len returns the number of handlers in the list.
func (l *HandlerList) Len() int {
	return len(l.list)
}

// PushBack pushes handler f to the back of the handler list.
func (l *HandlerList) PushBack(f func(*Request)) {
	l.PushBackNamed(NamedHandler{"__anonymous", f})
}

// PushBackNamed pushes named handler f to the back of the handler list.
func (l *HandlerList) PushBackNamed(n NamedHandler) {
	if cap(l.list) == 0 {
		l.list = make([]NamedHandler, 0, 5)
	}
	l.list = append(l.list, n)
}

// PushFront pushes handler f to the front of the handler list.
func (l *HandlerList) PushFront(f func(*Request)) {
	l.PushFrontNamed(NamedHandler{"__anonymous", f})
}

// PushFrontNamed pushes named handler f to the front of the handler list.
func (l *HandlerList) PushFrontNamed(n NamedHandler) {
	if cap(l.list) == len(l.list) {
		// Allocating new list required
		l.list = append([]NamedHandler{n}, l.list...)
	} else {
		// Enough room to prepend into list.
		l.list = append(l.list, NamedHandler{})
		copy(l.list[1:], l.list)
		l.list[0] = n
	}
}

// Remove removes a NamedHandler n
func (l *HandlerList) Remove(n NamedHandler) {
	l.RemoveByName(n.Name)
}

// RemoveByName removes a NamedHandler by name.
func (l *HandlerList) RemoveByName(name string) {
	for i := 0; i < len(l.list); i++ {
		m := l.list[i]
		if m.Name == name {
			// Shift array preventing creating new arrays
			copy(l.list[i:], l.list[i+1:])
			l.list[len(l.list)-1] = NamedHandler{}
			l.list = l.list[:len(l.list)-1]

			// decrement list so next check to length is correct
			i--
		}
	}
}

// SwapNamed will swap out any existing handlers with the same name as the
// passed in NamedHandler returning true if handlers were swapped. False is
// returned otherwise.
func (l *HandlerList) SwapNamed(n NamedHandler) (swapped bool) {
	for i := 0; i < len(l.list); i++ {
		if l.list[i].Name == n.Name {
			l.list[i].Fn = n.Fn
			swapped = true
		}
	}

	return swapped
}

// Swap will swap out all handlers matching the name passed in. The matched
// handlers will be swapped in. True is returned if the handlers were swapped.
func (l *HandlerList) Swap(name string, replace NamedHandler) bool {
	var swapped bool

	for i := 0; i < len(l.list); i++ {
		if l.list[i].Name == name {
			l.list[i] = replace
			swapped = true
		}
	}

	return swapped
}

// SetBackNamed will replace the named handler if it exists in the handler list.
// If the handler does not exist the handler will be added to the end of the list.
func (l *HandlerList) SetBackNamed(n NamedHandler) {
	if !l.SwapNamed(n) {
		l.PushBackNamed(n)
	}
}

// SetFrontNamed will replace the named handler if it exists in the handler list.
// If the handler does not exist the handler will be added to the beginning of
// the list.
func (l *HandlerList) SetFrontNamed(n NamedHandler) {
	if !l.SwapNamed(n) {
		l.PushFrontNamed(n)
	}
}

// Run executes all handlers in the list with a given request object.
func (l *HandlerList) Run(r *Request) {
	for i, h := range l.list {
		h.Fn(r)
		item := HandlerListRunItem{
			Index: i, Handler: h, Request: r,
		}
		if l.AfterEachFn != nil && !l.AfterEachFn(item) {
			return
		}
	}
}

// HandlerListLogItem logs the request handler and the state of the
// request's Error value. Always returns true to continue iterating
// request handlers in a HandlerList.
func HandlerListLogItem(item HandlerListRunItem) bool {
	if item.Request.Config.Logger == nil {
		return true
	}
	item.Request.Config.Logger.Log("DEBUG: RequestHandler",
		item.Index, item.Handler.Name, item.Request.Error)

	return true
}

// HandlerListStopOnError returns false to stop the HandlerList iterating
// over request handlers if Request.Error is not nil. True otherwise
// to continue iterating.
func HandlerListStopOnError(item HandlerListRunItem) bool {
	return item.Request.Error == nil
}

// WithAppendUserAgent will add a string to the user agent prefixed with a
// single white space.
func WithAppendUserAgent(s string) Option {
	return func(r *Request) {
		r.Handlers.Build.PushBack(func(r2 *Request) {
			AddToUserAgent(r, s)
		})
	}
}

// MakeAddToUserAgentHandler will add the name/version pair to the User-Agent request
// header. If the extra parameters are provided they will be added as metadata to the
// name/version pair resulting in the following format.
// "name/version (extra0; extra1; ...)"
// The user agent part will be concatenated with this current request's user agent string.
func MakeAddToUserAgentHandler(name, version string, extra ...string) func(*Request) {
	ua := fmt.Sprintf("%s/%s", name, version)
	if len(extra) > 0 {
		ua += fmt.Sprintf("/(%s)", strings.Join(extra, "; "))
	}
	return func(r *Request) {
		AddToUserAgent(r, ua)
	}
}

// MakeAddToUserAgentFreeFormHandler adds the input to the User-Agent request header.
// The input string will be concatenated with the current request's user agent string.
func MakeAddToUserAgentFreeFormHandler(s string) func(*Request) {
	return func(r *Request) {
		AddToUserAgent(r, s)
	}
}
